import { access, constants, mkdir, readFile, writeFile } from 'node:fs/promises';
import LuBan from '@luban/framework/LuBan';
import View from '@luban/framework/web/View';
import Engine from './main.ts';

export default class Index extends View {
  public engine: Engine = new Engine();

  /**
   * Whether to cache compiled template
   */
  public cache: boolean = false;

  /**
   * The mode of the newly created directory
   */
  public directoryMode: number = 0o777;

  /**
   * The rendered content HTML
   */
  public contentHtml: string = '';

  private hash(str: string): string {
    let h = 0n;
    for (let i = 0; i < str.length; i++) {
      h += BigInt(str.charCodeAt(i));
    }

    return h.toString();
  }

  private async checkCachedTemplate(template: string): Promise<void> {
    let viewData = await readFile(template, { encoding: 'utf8' });

    if (!this.cache) {
      this.engine.reset();
      this.engine.compile(viewData);
      return;
    }

    const runtime = this.context!.application.getRuntimePath();
    const suffix = template.substring(template.lastIndexOf('/') + 1);
    const fileName = this.hash(template) + '_' + suffix;

    // If dir exists
    try {
      await access(runtime, constants.R_OK | constants.W_OK);
    } catch (_e) {
      await mkdir(runtime, { mode: this.directoryMode, recursive: true });
    }

    // If cache exists
    try {
      await access(runtime + '/' + fileName, constants.R_OK);
      viewData = await readFile(runtime + '/' + fileName, { encoding: 'utf8' });
      this.engine.setCompiled(viewData);
    } catch (_e) {
      this.engine.reset();
      const compiled = this.engine.compile(viewData);
      await writeFile(runtime + '/' + fileName, compiled, { encoding: 'utf8' });
    }
  }

  /**
   * @inheritdoc
   */
  public override async renderFile(file: string, parameters: any): Promise<string> {
    await this.checkCachedTemplate(file);
    this.contentHtml = this.engine.run({
      $this: this,
      $data: parameters,
    });

    if (this.enableLayout) {
      const layoutFile = LuBan.getPathAlias('@' + this.layout + this.defaultExtension);

      await this.checkCachedTemplate(layoutFile);
      this.contentHtml = this.engine.run({
        $this: this,
        $contentHtml: this.contentHtml,
        $data: parameters,
      });
    }

    return this.contentHtml;
  }
}
